package mint.modules.generator.expression;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

import org.antlr.runtime.tree.Tree;

import mint.modules.generator.CounterManager;
import mint.modules.generator.Scope;
import mint.modules.symbol.ClassSymbol;
import mint.modules.symbol.ConstructorSymbol;
import mint.modules.symbol.Symbol;
import mint.modules.symbol.SymbolType;
import mint.modules.type.Type;

public class ClassInstanceCreationExprNode extends Expression {
	private String className;
	private List<Expression> arguments;
	private List<String> errors;
	private int line;

	public ClassInstanceCreationExprNode(Scope theParent, ExpressionType exprType, Tree classCreationTree) {
		super(theParent, exprType);
		Tree argumentTree;
		
		line = classCreationTree.getLine();
		errors = new ArrayList<String>();
		arguments = new ArrayList<Expression>();
		
		className = classCreationTree.getChild(0).getText();
		argumentTree = classCreationTree.getChild(1).getChild(0);
		
		if (argumentTree != null)
			for (int i = 0; i < argumentTree.getChildCount(); i++)
			{
				arguments.add(new ExprNode(theParent, ExpressionType.Right, argumentTree.getChild(i)));
				CounterManager.getInstance().incrementParameter();
			}
	}

	@Override
	public void testSemantic() {
		Symbol symbol = searchSymbol(className);
		
		for (Expression expr : arguments)
			expr.testSemantic();
		
		if (getExpressionType() == ExpressionType.Left || getExpressionType() == ExpressionType.RightLeft)
			errors.add(line + ": class instance must be on the right side of a assignation");
		else if (symbol == null)
			errors.add(line + ": the class " + className + " isn't defined in this scope");
		else
		{
			if (symbol.getSymbolType() != SymbolType.Class)
				errors.add(line + ": " + className + " isn't a class name");
			else
			{
				ClassSymbol classSymbol = (ClassSymbol)symbol;				
				symbol = classSymbol.getClassNode().searchSymbol("<init>", getInnerClass(), false);
				
				if (symbol == null)
				{
					errors.add(line + ": " + className + " doesn't have a constructor definition");
				}
				else
				{
					if(symbol.getSymbolType() != SymbolType.Constructor)
						errors.add(line + ": " + className + " doesn't have a constructor definition");
					else
					{
						ConstructorSymbol ctorSymbol = (ConstructorSymbol)symbol;
						
						if (ctorSymbol.getParameterListSize() != arguments.size())
							errors.add(line + ": " + className + " constructor take " + 
										ctorSymbol.getParameterListSize() + " but received " +
										arguments.size() + " arguments");
						else
						{
							for (int i = 0; i < ctorSymbol.getParameterListSize(); i++)
							{
								if (ctorSymbol.getParameter(i).getType().equals(Type.floatType))
								{
									if(!(arguments.get(i).getType().equals(Type.intType) || arguments.get(i).getType().equals(Type.floatType)))
										errors.add(line + ": " + className + " " + i + 
												"th constructor argument expected type: " + ctorSymbol.getParameter(i).getType().getCompleteName()
												+ " received type: " + arguments.get(i).getType().getCompleteName());
								} else if (!ctorSymbol.getParameter(i).getType().equals(arguments.get(i).getType()))
									if(!arguments.get(i).getType().equals(Type.objectNullType))
										errors.add(line + ": " + className + " " + i + 
											"th constructor argument expected type: " + ctorSymbol.getParameter(i).getType().getCompleteName()
											+ " received type: " + arguments.get(i).getType().getCompleteName());
							}
						}
					}
				}
			}
		}
	}

	@Override
	public List<String> getSemanticErrors() {
		for (Expression expr : arguments)
			errors.addAll(expr.getSemanticErrors());
		return errors;
	}

	@Override
	public void emit(Formatter output) {
		int i;
		Symbol symbol = searchSymbol(className);
		ClassSymbol classSymbol = (ClassSymbol)symbol;
		ConstructorSymbol ctorSymbol = (ConstructorSymbol)classSymbol.getClassNode().searchSymbol("<init>", getInnerClass(), false);
		
		output.format("new %s\n", className);
		output.format("dup\n");
		
		i = 0;
		for (Expression expr : arguments)
		{
			expr.emit(output);
			if (ctorSymbol.getParameter(i).getType().equals(Type.floatType))
				if(expr.getType().equals(Type.intType))
					output.format("i2f\n");
		}
		output.format("invokespecial %s/<init>(", className);
		for (i = 0; i < ctorSymbol.getParameterListSize(); i++)
			output.format("%s", ctorSymbol.getParameter(i).getType().emit());
		output.format(")V\n");
	}

	@Override
	public int getLine() {
		return line;
	}

	@Override
	public Type getType() {
		return Type.createObjectType(className);
	}

	@Override
	public void leftEmit(Formatter output) {}

}
